﻿using System;
using System.Globalization;
using UnityEngine;

/// <summary>
/// All useful math utilities...
/// </summary>
public class MathUtil
{
	public static bool IsApproximatelyEqualTo(double initialValue, double value)
	{
		return IsApproximatelyEqualTo(initialValue, value, 0.00001);
	}

	public static bool IsApproximatelyEqualTo(double initialValue, double value, double maximumDifferenceAllowed)
	{
		// Handle comparisons of floating point values that may not be exactly the same
		return (Math.Abs(initialValue - value) < maximumDifferenceAllowed);
	}
    public static byte ParseByte(string str)
    {
        if (string.IsNullOrEmpty(str))
        {
            Debug.LogError($"ParseByte: input string is nul");
            return 0;
        }
        if (byte.TryParse(str, out byte ret))
        {
            return ret;
        }
        Debug.LogError($"ParseByte Error: {str}");
        return 0;
    }
    public static int ParseInt(string str)
    {
        if (string.IsNullOrEmpty(str))
        {
            Debug.LogError($"ParseInt: input string is nul");
            return 0;
        }
        if (int.TryParse(str, out int ret))
        {
            return ret;
        }
        Debug.LogError($"ParseInt Error: {str}");
        return 0;
    }
    public static long ParseLong(string str)
    {
        if (string.IsNullOrEmpty(str))
        {
            Debug.LogError($"ParseLong: input string is nul");
            return 0;
        }
        if (long.TryParse(str, out long ret))
        {
            return ret;
        }
        Debug.LogError($"ParseLong Error: {str}");
        return 0;
    }
    public static double ParseDouble(string str)
    {
        if (string.IsNullOrEmpty(str))
        {
            Debug.LogError($"ParseDouble: input string is nul");
            return 0;
        }
        if (double.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out double ret))
        {
            return ret;
        }
        Debug.LogError($"ParseDouble Error: {str}");
        return 0;
    }
    public static float ParseFloat(string str)
    {
        if (string.IsNullOrEmpty(str))
        {
            Debug.LogError($"ParseFloat: input string is nul");
            return 0;
        }
        if (float.TryParse(str,NumberStyles.Any, CultureInfo.InvariantCulture, out float ret))
        {
            return ret;
        }
        Debug.LogError($"ParseFloat Error: {str}");
        return 0;
	}

	public static Vector3 GetXZPosition (Vector3 position)
	{
		return new Vector3(position.x, 0f, position.z);
	}

	public static float DistanceXZ (Vector3 from, Vector3 to)
	{
		float x = from.x - to.x;
		float z = from.z - to.z;

		return Mathf.Sqrt(x * x + z * z);
	}

	public static float SqrDistanceXZ (Vector3 from, Vector3 to)
	{
		float x = from.x - to.x;
		float z = from.z - to.z;

		return x * x + z * z;
	}

	public static float SqrDistanceUI (Vector3 from, Vector3 to)
	{
		float x = from.x - to.x;
		float y = from.y - to.y;

		return x * x + y * y;
	}

	public static float Distance2D(float x1, float y1, float x2, float y2)
	{
		float x = x1 - x2;
		float y = y1 - y2;

		return Mathf.Sqrt(x * x + y * y);
	}

	public static Quaternion LookRotationXZ (Vector3 direction)
	{
		return Quaternion.LookRotation(new Vector3(direction.x, 0, direction.z));
	}

    /// <summary>
    /// http://stackoverflow.com/questions/4061576/finding-points-on-a-rectangle-at-a-given-angle
    /// </summary>
    /// <returns>根据角度计算矩形上的点，center为0时，返回的点的坐标的原点是矩形的左下角，center设置为size/2，则返回的坐标的原点是矩形中心.</returns>
    /// <param name="center">Center.</param>
    /// <param name="size">Size.</param>
    /// <param name="radius">Radius.</param>
    public static Vector2 FindPointOnRectangle(Vector2 center, Vector2 size, float radius)
    {
        float twoPI = Mathf.PI*2;

        while(radius < -Mathf.PI)
        {
            radius += twoPI;
        }

        float rectAtan = Mathf.Atan2(size.y, size.x);
        float tanRadius = Mathf.Tan(radius);
        int region;

        if((radius > -rectAtan) && (radius <= rectAtan))
        {
            region = 1;
        }
        else if ((radius > rectAtan) && (radius <= (Mathf.PI - rectAtan))) 
        {
            region = 2;
        }
        else if ((radius > (Math.PI - rectAtan)) || (radius <= -(Mathf.PI - rectAtan)))
        {
            region = 3;
        }
        else
        {
            region = 4;
        }

        Vector2 edgePoint = new Vector2(size.x / 2, size.y / 2);
        edgePoint -= center;
        int xFactor = 1;
        int yFactor = 1;

        switch (region) {
            case 1: xFactor = 1;  yFactor =  1; break;
            case 2: xFactor = 1;  yFactor =  1; break;
            case 3: xFactor = -1; yFactor = -1; break;
            case 4: xFactor = -1; yFactor = -1; break;
        }

        if ((region == 1) || (region == 3)) 
        {
            edgePoint.x += (xFactor * (size.x / 2f));                                     // "Z0"
            edgePoint.y += (yFactor * (size.x / 2f) * tanRadius);
        } 
        else
        {
            edgePoint.x += (xFactor * (size.y / (2f * tanRadius)));                        // "Z1"
            edgePoint.y += (yFactor * (size.y /  2f));
        }

        return edgePoint;
    }

    public static void ReverseXY(ref Vector2Int vector)
    {
        int temp = vector.x;
        vector.x = vector.y;
        vector.y = temp;
    }

	public static int RoundToInt(float val)
	{
		return (int) (val + Mathf.Sign(val) * .5f);
	}

    public static long RoundToLong(float val)
    {
        return (long)(val + Mathf.Sign(val) * .5f);
    }

	public static long RoundToLong(double val)
	{
		return (long)(val + Math.Sign(val) * .5f);
	}

    /// <summary>
    /// Extract translation from transform matrix.
    /// </summary>
    /// <param name="matrix">Transform matrix. This parameter is passed by reference
    /// to improve performance; no changes will be made to it.</param>
    /// <returns>
    /// Translation offset.
    /// </returns>
    public static Vector3 ExtractTranslationFromMatrix(ref Matrix4x4 matrix) {
        Vector3 translate;
        translate.x = matrix.m03;
        translate.y = matrix.m13;
        translate.z = matrix.m23;
        return translate;
    }
    
    /// <summary>
    /// Extract rotation quaternion from transform matrix.
    /// </summary>
    /// <param name="matrix">Transform matrix. This parameter is passed by reference
    /// to improve performance; no changes will be made to it.</param>
    /// <returns>
    /// Quaternion representation of rotation transform.
    /// </returns>
    public static Quaternion ExtractRotationFromMatrix(ref Matrix4x4 matrix) {
        Vector3 forward;
        forward.x = matrix.m02;
        forward.y = matrix.m12;
        forward.z = matrix.m22;
    
        Vector3 upwards;
        upwards.x = matrix.m01;
        upwards.y = matrix.m11;
        upwards.z = matrix.m21;
    
        return Quaternion.LookRotation(forward, upwards);
    }
    
    /// <summary>
    /// Extract scale from transform matrix.
    /// </summary>
    /// <param name="matrix">Transform matrix. This parameter is passed by reference
    /// to improve performance; no changes will be made to it.</param>
    /// <returns>
    /// Scale vector.
    /// </returns>
    public static Vector3 ExtractScaleFromMatrix(ref Matrix4x4 matrix) {
        Vector3 scale;
        scale.x = new Vector4(matrix.m00, matrix.m10, matrix.m20, matrix.m30).magnitude;
        scale.y = new Vector4(matrix.m01, matrix.m11, matrix.m21, matrix.m31).magnitude;
        scale.z = new Vector4(matrix.m02, matrix.m12, matrix.m22, matrix.m32).magnitude;
        return scale;
    }
    
    /// <summary>
    /// Extract position, rotation and scale from TRS matrix.
    /// </summary>
    /// <param name="matrix">Transform matrix. This parameter is passed by reference
    /// to improve performance; no changes will be made to it.</param>
    /// <param name="localPosition">Output position.</param>
    /// <param name="localRotation">Output rotation.</param>
    /// <param name="localScale">Output scale.</param>
    public static void DecomposeMatrix(ref Matrix4x4 matrix, out Vector3 localPosition, out Quaternion localRotation, out Vector3 localScale) {
        localPosition = ExtractTranslationFromMatrix(ref matrix);
        localRotation = ExtractRotationFromMatrix(ref matrix);
        localScale = ExtractScaleFromMatrix(ref matrix);
    }
    
    /// <summary>
    /// Set transform component from TRS matrix.
    /// </summary>
    /// <param name="transform">Transform component.</param>
    /// <param name="matrix">Transform matrix. This parameter is passed by reference
    /// to improve performance; no changes will be made to it.</param>
    public static void SetTransformFromMatrix(Transform transform, ref Matrix4x4 matrix) {
        transform.localPosition = ExtractTranslationFromMatrix(ref matrix);
        transform.localRotation = ExtractRotationFromMatrix(ref matrix);
        transform.localScale = ExtractScaleFromMatrix(ref matrix);
    }
    
    
    // EXTRAS!
    
    /// <summary>
    /// Identity quaternion.
    /// </summary>
    /// <remarks>
    /// <para>It is faster to access this variation than <c>Quaternion.identity</c>.</para>
    /// </remarks>
    public static readonly Quaternion IdentityQuaternion = Quaternion.identity;
    /// <summary>
    /// Identity matrix.
    /// </summary>
    /// <remarks>
    /// <para>It is faster to access this variation than <c>Matrix4x4.identity</c>.</para>
    /// </remarks>
    public static readonly Matrix4x4 IdentityMatrix = Matrix4x4.identity;
    
    /// <summary>
    /// Get translation matrix.
    /// </summary>
    /// <param name="offset">Translation offset.</param>
    /// <returns>
    /// The translation transform matrix.
    /// </returns>
    public static Matrix4x4 TranslationMatrix(Vector3 offset) {
        Matrix4x4 matrix = IdentityMatrix;
        matrix.m03 = offset.x;
        matrix.m13 = offset.y;
        matrix.m23 = offset.z;
        return matrix;
    }

    public static bool IsLineInsection(ref Vector2 v1, ref Vector2 v2, ref Vector2 v3, ref Vector2 v4)
    {
        float d = (v4.y - v3.y) * (v2.x - v1.x) - (v4.x - v3.x) * (v1.y - v1.y);
        float u = (v4.x - v3.x) * (v1.y - v3.y) - (v4.y - v3.y) * (v1.x - v3.x);
        float v = (v2.x - v1.x) * (v1.y - v3.y) - (v2.y - v1.y) * (v1.x - v3.x);
        if (d < 0)
        {
            d = -d;
            u = -u;
            v = -v;
        }
        return (u >= 0 && u <= d) && (v >= 0 && v <= d);
    }

    private static float CrossVal(ref Vector2 u, ref Vector2 v)
    {
        return u.x * v.y - u.y * v.x;
    }

    public static bool IsPointInTriangle(ref Vector2 a, ref Vector2 b, ref Vector2 c, ref Vector2 p)
    {
        Vector2 v0 = c - a;
        Vector2 v1 = b - a;
        Vector2 v2 = p - a;
        float u = CrossVal(ref v2, ref v0);
        float v = CrossVal(ref v1, ref v2);
        float d = CrossVal(ref v1, ref v0);
        if (d < 0)
        {
            d = -d;
            u = -u;
            v = -v;
        }
        return u >= 0 && v >= 0 && (u + v) <= d;
    }

    public static bool IsTriangleInsect(Vector2 t10, Vector2 t11, Vector2 t12, Vector2 t20, Vector2 t21, Vector2 t22)
    {
        if (IsLineInsection(ref t10, ref t11, ref t20, ref t21) ||
            IsLineInsection(ref t10, ref t11, ref t20, ref t22) ||
            IsLineInsection(ref t10, ref t11, ref t21, ref t22) ||
            IsLineInsection(ref t10, ref t12, ref t20, ref t21) ||
            IsLineInsection(ref t10, ref t12, ref t20, ref t22) ||
            IsLineInsection(ref t10, ref t12, ref t21, ref t22) ||
            IsLineInsection(ref t11, ref t12, ref t20, ref t21) ||
            IsLineInsection(ref t11, ref t12, ref t20, ref t22) ||
            IsLineInsection(ref t11, ref t12, ref t21, ref t22) ||
            IsPointInTriangle(ref t10, ref t11, ref t12, ref t20) || 
            IsPointInTriangle(ref t10, ref t11, ref t12, ref t21) || 
            IsPointInTriangle(ref t10, ref t11, ref t12, ref t22) || 
            IsPointInTriangle(ref t20, ref t21, ref t22, ref t10) || 
            IsPointInTriangle(ref t20, ref t21, ref t22, ref t11) || 
            IsPointInTriangle(ref t20, ref t21, ref t22, ref t12))
        {
            return true;
        }
        return false;
    }

    public static long ClampLong(long value, long min, long max)
    {
        if (value < min)
            value = min;
        else if (value >  max)
            value = max;
        return value;
    }
    
    public static int ClampInt(int value, int min, int max)
    {
        if (value < min)
            value = min;
        else if (value >  max)
            value = max;
        return value;
    }
    
    /// <summary>
    /// 点pB到直线pA1,pA2的距离
    /// </summary>
    /// <param name="pB"></param>
    /// <param name="pA1"></param>
    /// <param name="pA2"></param>
    /// <returns></returns>
    public static float DistanceByArea(ref Vector3 pB, ref Vector3 pA1, ref Vector3 pA2)
    {
        Vector3 a2A1 = pA1 - pA2;
        Vector3 a2B = pB - pA2;

        Vector3 normal = Vector3.Cross(a2A1, a2B);
        Vector3 a2A1Temp = Vector3.Cross(a2A1, normal).normalized;

        return Mathf.Abs(Vector3.Dot(a2B, a2A1Temp));
    }
    
    //Ease InCubic
    public static float InCubic(float t)
    {
        return t * t * t;  
    }
    //Ease InQuad
    public static float InQuad(float t)
    {
        return t* t;
    }
}

public static class MathExtensions
{
    public static Vector3 Add(this Vector3 v1, Vector2 v2)
    {
        return new Vector3(v1.x + v2.x, v1.y + v2.y, v1.z);
    }

    public static Vector3 Subtract(this Vector3 v1, Vector2 v2)
    {
        return new Vector3(v1.x - v2.x, v1.y - v2.y, v1.z);
    }
}

